package com.common.orm;

import diplom.common.orm.annotation.ORMField;
import diplom.common.orm.annotation.ORMPrimaryKey;
import diplom.common.orm.annotation.ORMTable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

/**
 * @author Dmitry Savchenko
 */
class ClassProcessor {

    private List<MethodColumnPair> methods = null;

    public ClassData getClassData(Class clazz) {
        methods = new ArrayList<MethodColumnPair>(10);
        ClassData cd = new ClassData();
        try {
            String tableName = getTableName(clazz);
            cd.setClazz(clazz);
            cd.setTableName(tableName);
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
        try {
            processClassFields(cd);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        HashMap<String, MethodColumnPair> hashMap = new HashMap<String, MethodColumnPair>(10);
        for (MethodColumnPair mcp : methods) {
            hashMap.put(mcp.getColumn(), mcp);
            if (mcp.isPrimaryKey()) {
                if (cd.getPrimaryKey() != null) {
                    OrmUtils.throwOutErrorExceptionMessage("PojoObject " + cd.getClazz().getName() + " already has primary key " + cd.getPrimaryKey().getColumn());
                    return null;//just return from the method. It is a error. Cannot be more than one primary key
                }

                cd.setPrimaryKey(mcp);
            }
        }
        cd.setColumns(methods.toArray(new MethodColumnPair[0]));
        cd.setColumnsMap(hashMap);
        methods = null;
        return cd;
    }

    private void processClassFields(ClassData cd) throws Exception {
        Class clazz = cd.getClazz();
        Class currentClass = clazz;
        while (currentClass != null) {
            java.lang.reflect.Field[] fields = currentClass.getDeclaredFields();
            for (java.lang.reflect.Field f : fields) {
                ORMField fieldAnnotation = f.getAnnotation(ORMField.class);
                if (fieldAnnotation != null) {
                    processField(cd, fieldAnnotation, f);
                }
            }
            currentClass = currentClass.getSuperclass();
        }
    }

    private String makeFirstLetterBig(String string) {
        if (string == null || string.length() == 0) {
            return "";
        }
        String firstLetter = String.valueOf(string.charAt(0)).toUpperCase();
        return firstLetter + string.substring(1);
    }

    private void processField(ClassData cd, ORMField fieldAnnotation, java.lang.reflect.Field field) throws Exception {
        Class fieldType = field.getType();
        String fieldName = field.getName();
        String getterMethodName = "get" + makeFirstLetterBig(fieldName);
        Method getter = cd.getClazz().getMethod(getterMethodName);
        String setterMethodName = "set" + makeFirstLetterBig(fieldName);
        Method setter = cd.getClazz().getMethod(setterMethodName, fieldType);

        MethodColumnPair mcp = new MethodColumnPair();
        mcp.setColumn(fieldAnnotation.columnName());
        mcp.setGetter(getter);
        mcp.setSetter(setter);
        processResultSetFields(fieldType.getName(), mcp);
        mcp.setFieldType(fieldType);
        if (field.getAnnotation(ORMPrimaryKey.class) != null) {
            mcp.setPrimaryKey(true);
        }
        methods.add(mcp);
    }

    private void processResultSetFields(String fieldType, MethodColumnPair mcp) throws NoSuchMethodException {
        Method getter = null;
        Method setter = null;
        PreparedStatement ps;
        if ("int".equals(fieldType) || "java.lang.Integer".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getInt", int.class);
            setter = PreparedStatement.class.getMethod("setInt", int.class, int.class);
        } else if ("long".equals(fieldType) || "java.lang.Long".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getLong", int.class);
            setter = PreparedStatement.class.getMethod("setLong", int.class, long.class);
        } else if ("double".equals(fieldType) || "java.lang.Double".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getDouble", int.class);
            setter = PreparedStatement.class.getMethod("setDouble", int.class, double.class);
        } else if ("float".equals(fieldType) || "java.lang.Float".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getFloat", int.class);
            setter = PreparedStatement.class.getMethod("setFloat", int.class, float.class);
        } else if ("boolean".equals(fieldType) || "java.lang.Boolean".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getBoolean", int.class);
            setter = PreparedStatement.class.getMethod("setBoolean", int.class, boolean.class);
        } else if ("java.lang.String".equals(fieldType)||"[Ljava.lang.Integer;".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getString", int.class);
            setter = PreparedStatement.class.getMethod("setString", int.class, java.lang.String.class);
        } else if ("java.sql.Date".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getDate", int.class);
            setter = PreparedStatement.class.getMethod("setDate", int.class, java.sql.Date.class);
        } else if ("java.util.Date".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getDate", int.class);
            setter = PreparedStatement.class.getMethod("setDate", int.class, java.sql.Date.class);
        } else if ("java.sql.Timestamp".equals(fieldType)) {
            getter = ResultSet.class.getMethod("getTimestamp", int.class);
            setter = PreparedStatement.class.getMethod("setTimestamp", int.class, java.sql.Timestamp.class);
        } else {
            OrmUtils.throwOutErrorExceptionMessage("Unknown field type:" + fieldType);
        }

        mcp.setPreparedStatementValueSetter(setter);
        mcp.setResultSetGetter(getter);

    }

    private String getTableName(Class clazz) throws Exception {
        Annotation a = getTableAnnotation(clazz);
        ORMTable tableAnnotation = (ORMTable) a;
        return tableAnnotation.name();
    }

    private Annotation getTableAnnotation(Class clazz) throws Exception {
        for (Annotation a : clazz.getDeclaredAnnotations()) {
            if (a.annotationType() == ORMTable.class) {
                return a;
            }
        }

        throw new Exception("There is no annotation Table on the class " + clazz.getName());
    }
}
